package com.ttxs.common.security.aspect;

import com.ttxs.api.login.RemoteLoginService;
import com.ttxs.common.core.annotation.RemoteLogin;
import com.ttxs.common.core.vo.R;
import com.ttxs.common.core.vo.RemoteLoginVO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Aspect
@Component
@EnableFeignClients(basePackages = "com.ttxs.api.login")
public class RemoteLoginAspect {
    @Resource
    private RemoteLoginService remoteLoginService;

    @Around(value = "@annotation(remoteLogin)")
    public Object around(ProceedingJoinPoint joinPoint, RemoteLogin remoteLogin) {
        try {
            Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
            if (method.getReturnType() != R.class) {
                return R.error(method.getName() + "返回值只能是：" + R.class.getName() + "类型");
            }
            Object[] args = joinPoint.getArgs();
            R localLoginResult = (R) joinPoint.proceed(args);
            if(localLoginResult.getCode() == R.SUCCESS) {
                RemoteLoginVO remoteLoginVO = createRemoteLoginVO(method, args, remoteLogin);
                R remoteLoginResult = remoteLoginService.login(remoteLoginVO);
                if (log.isDebugEnabled()) {
                    log.debug("remote remoteLoginResult:{}", remoteLoginResult);
                }
                return remoteLoginResult;
            } else {
                return localLoginResult;
            }
        } catch (Throwable ex) {
            log.error(ex.getMessage(), ex);
            return R.error(ex.getMessage());
        }
    }

    private RemoteLoginVO createRemoteLoginVO(Method method, Object[] args, RemoteLogin remoteLogin) {
        Parameter[] parameters = method.getParameters();

        Map<String, Parameter> parameterMap = Arrays.stream(parameters).collect(Collectors.toMap(Parameter::getName, Function.identity()));
        if (log.isDebugEnabled()) {
            log.debug("args:{}", Arrays.asList(args));
            log.debug("parameterMap:{}", parameterMap);
        }
        Class entityClass = remoteLogin.entityClass();
        String username = null;
        String password = null;
        String platformName = remoteLogin.platformName();
        Assert.hasText(platformName, "platformName不能为空");
        // 从方法入参或实体类中获取username和password
        if (entityClass == Void.class) {
            username = (String) args[getParameterIndex(remoteLogin.username(), parameters)];
            password = (String) args[getParameterIndex(remoteLogin.password(), parameters)];
        } else {
            Object obj = args[getParameterIndex(entityClass, parameters)];
            username = getFieldValue(obj, remoteLogin.username());
            password = getFieldValue(obj, remoteLogin.password());
        }
        RemoteLoginVO remoteLoginVO = new RemoteLoginVO();
        remoteLoginVO.setUsername(username);
        remoteLoginVO.setPassword(password);
        remoteLoginVO.setPlatformName(platformName);
        remoteLoginVO.setAlg(remoteLogin.alg());
        return remoteLoginVO;
    }

    @SneakyThrows
    private <T> T getFieldValue(Object obj, String fieldName) {
        Field declaredField = obj.getClass().getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        return (T)declaredField.get(obj);
    }

    private int getParameterIndex(String name, Parameter[] parameters) {
        for (int i = 0; i < parameters.length; i++) {
            if(parameters[i].getName().equals(name)) {
                return i;
            }
        }
        return -1;
    }

    private int getParameterIndex(Class clz, Parameter[] parameters) {
        for (int i = 0; i < parameters.length; i++) {
            if(parameters[i].getType() == clz) {
                return i;
            }
        }
        return -1;
    }


}
